#
# Copyright (c) 2025 CAX Conversion Project
#

# -*- coding: utf-8 -*-
import os
import sys
from pyNastran.bdf.bdf import BDF
import json
import logging
import time
import pygltflib
import numpy as np
import re

logger = logging.getLogger(__name__)

CENTER_CARD_NAME = ['CQUAD4', 'CSHEAR', 'CTRIA3', 'CBAR', 'CROD']

#####################################################################
def create_gltf_from_bdf(json_string):
    try:
        # 解析 JSON 字符串为字典
        try:
            file_paths = json_string
        except json.JSONDecodeError as e:
            result = {"error": str(e)}
            return result


        bdf_path = file_paths["bdf_path"]
        material_path = file_paths["material_path"]
        parsed_bdf_path = file_paths["parsed_bdf_path"]
        gltf_path = file_paths["gltf_path"]
        xref_path = file_paths["xref_path"]

        # 解析bdf文件
        parsed_bdf = parse_bdf(bdf_path, material_path)
        os.makedirs(os.path.dirname(parsed_bdf_path), exist_ok=True)
        os.makedirs(os.path.dirname(gltf_path), exist_ok=True)
        os.makedirs(os.path.dirname(xref_path), exist_ok=True)
        with open(parsed_bdf_path, 'w', encoding='utf-8') as f:
            json.dump(parsed_bdf, f, ensure_ascii=False, indent=4)


        # 创建gltf文件，包括：.gltf 和 .bin
        create_gltf_from_parsed_bdf(parsed_bdf, gltf_path, xref_path)
    except Exception as e:
        result = {"error": str(e)}
        return result

def parse_bdf_stats(json_string):
    try:
        # 解析 JSON 字符串为字典
        file_paths = json_string
        bdf_path = file_paths["bdf_path"]
        material_path = file_paths["material_path"]

        # 读取 BDF 文件并解析
        model = BDF()
        model.read_bdf(material_path, punch=True, xref=True)
        model.read_bdf(bdf_path, punch=True, xref=True)

        # 获取 BDF 统计信息
        stats_str = model.get_bdf_stats()
        stats_dict = {}

        # 正则表达式匹配
        pattern = re.compile(r'(\w+)\s*:\s*(\d+)')

        # 分段解析
        sections = stats_str.split('\n\n')
        for section in sections:
            lines = section.strip().split('\n')
            if len(lines) > 1:
                section_key = lines[0].strip().split(':')[0]  # 截取掉冒号及后面的值
                if section_key == 'SOL':
                    sol_value = lines[1].strip().split()[1]
                    stats_dict['SOL'] = sol_value
                else:
                    stats_dict[section_key] = {}
                    for line in lines[1:]:
                        match = pattern.search(line)
                        if match:
                            key, value = match.groups()
                            stats_dict[section_key][key] = int(value)

        # 处理字典
        process_dict(stats_dict)
        result = stats_dict

    except Exception as e:
        result = {"error": str(e)}

    return result

def convert_to_card_format(d):
    result = []
    for key, value in d.items():
        result.append({'card': key, 'value': value})
    return result

def process_dict(d):
    for key, value in d.items():
        if isinstance(value, dict):
            d[key] = convert_to_card_format(value)

#####################  材料文件bdf  ###########################
def read_materials(material_path):
    try:

        # 读取 BDF 文件并解析
        model = BDF()
        model.read_bdf(material_path, punch=True, xref=True)

        # 获取 BDF 卡片列表
        card_types = list(model.card_count.keys())

        # 根据材料卡片类型分类获取
        material_collection = model.get_cards_by_card_types(card_types)

        # 创建字典保存信息
        material_map = {}
        for key in material_collection.keys():
            material_cards = material_collection.get(key)
            material_list = []
            for material_card in material_cards:
                properties = {}
                for name in sorted(material_card.object_attributes()):
                    properties[name] = getattr(material_card, name)
                material_list.append(properties)
            material_map[key] = material_list

        return material_map

    except Exception as e:
        result = {"error": str(e)}

    return result

###########################模型解析###################################
def parse_bdf(bdf_path, material_path):
    """
    parse bdf file by pyNastran and import related bdf data to mongodb

    Parameters
    ----------
    file_path : str
        the path of bdf file
    """

    start_time = time.time()

    # read bdf file using pyNastran
    # logging.basicConfig(level=logging.DEBUG)
    # model = BDF(log=logging.getLogger())
    model = BDF()
    model.read_bdf(material_path, punch=True, xref=True)
    model.read_bdf(bdf_path, punch=True, xref=True)
    model.set_error_storage(stop_on_parsing_error=False, stop_on_xref_error=False)

    try:

        cards_with_values = []
        cards_with_values = list(model.card_count)

        if len(cards_with_values) != 0:
            parsed_bdf = []
            collecion_bdf = []
            # 移除指定的卡片类型
            bdf_cards_pre = [card for card in cards_with_values if card not in ('CWELD', 'PWELD')]
            bdf_cards = model.get_cards_by_card_types(card_types=bdf_cards_pre)


            for bdf_card in bdf_cards:
                card_items = bdf_cards[bdf_card]
                for item in card_items:
                    str_item = json.dumps(item.__dict__, default=_dump_cust_class)
                    json_item = json.loads(str_item)
                    json_item['type'] = item.type

                    # 处理单元
                    if item.type in CENTER_CARD_NAME:
                        json_item['center_point'] = calculate_center_point(json_item)
                        # 特殊处理
                        # 对cp为8181bdf数据特殊处理
                        if item.type == "CBAR":
                            if not json_item["ga_ref"] is None \
                                and (json_item["ga_ref"]["cp"] != 0):
                                _transform_coordinate(json_item["ga_ref"])
                            if not json_item["gb_ref"] is None \
                                and json_item["gb_ref"]["cp"] != 0:
                                _transform_coordinate(json_item["gb_ref"])
                        else:
                            if "nodes_ref" in json_item.keys() \
                                and (not json_item["nodes_ref"] is None):
                                for nodes_ref_item in json_item["nodes_ref"]:
                                    if nodes_ref_item["cp"] != 0:
                                        _transform_coordinate(nodes_ref_item)

                    # 添加至bdf_card_collection
                    parsed_bdf.append(json_item)

        else:
            raise Exception("PARSE FAILED")

    except Exception as ex:
        logger.debug(ex)
        raise
    end_time = time.time()
    logger.debug("prase bdf:" + bdf_path + " in " +
                 str(end_time - start_time) + " seconds")
    print("prase bdf:" + bdf_path + " in " +
          str(end_time - start_time) + " seconds")

    return parsed_bdf

def _dump_cust_class(obj):
    """
    dump custom class

    Parameters
    ----------
    obj: obj to parse

    """
    if isinstance(obj, np.ndarray):
        return obj.tolist()
    else:
        return json.loads(json.dumps(obj.__dict__, default=_dump_cust_class))

def _transform_coordinate(parameter):
    """
    将局部坐标系中的点坐标转换为全局坐标系中的点坐标

    Parameters
    ----------
    parameter: dict
        {"nid": 111111, "cp": 123, "xyz": [1,2,3], ...,}

    Returns
    -------
    None
    """
    if parameter["cp"] != 0:
        new_xyz = _transform(parameter["xyz"], parameter["cp_ref"])
        parameter["xyz"] = new_xyz


def _transform(xyz, cp_ref):
    """
    对cp值不为0的类型局部坐标系进行转换

    Parameters
    ----------
    xyz: list
        局部坐标系中的空间坐标点
    cp_ref: dict
        局部坐标系的相关信息，如坐标轴方向向量、原点坐标等

    Returns
    -------
    result: list
    """
    # 获取局部坐标系的坐标轴单位向量及原点坐标
    local_i = cp_ref["i"]
    local_j = cp_ref["j"]
    local_k = cp_ref["k"]
    local_origin = cp_ref["origin"]
    # 设置全局坐标系的坐标轴单位向量及原点坐标
    global_i = [1, 0, 0]
    global_j = [0, 1, 0]
    global_k = [0, 0, 1]
    global_origin = [0, 0, 0]
    unit_matrix = np.mat([[global_i[0], global_j[0], global_k[0]],
                          [global_i[1], global_j[1], global_k[1]],
                          [global_i[2], global_j[2], global_k[2]]])
    # 计算平移变换
    change_x = local_origin[0] - global_origin[0]
    change_y = local_origin[1] - global_origin[1]
    change_z = local_origin[2] - global_origin[2]
    # 按列求解转换矩阵
    transform_matrix = []
    colum_0 = np.linalg.solve(unit_matrix, local_i)
    transform_matrix.append(list(colum_0))
    colum_1 = np.linalg.solve(unit_matrix, local_j)
    transform_matrix.append(list(colum_1))
    colum_2 = np.linalg.solve(unit_matrix, local_k)
    transform_matrix.append(list(colum_2))
    # 根据变换公式求解全局坐标系中的点坐标
    x = change_x + xyz[0] * transform_matrix[0][0] + xyz[1] * transform_matrix[1][0] + xyz[2] * transform_matrix[2][0]
    y = change_y + xyz[0] * transform_matrix[0][1] + xyz[1] * transform_matrix[1][1] + xyz[2] * transform_matrix[2][1]
    z = change_z + xyz[0] * transform_matrix[0][2] + xyz[1] * transform_matrix[1][2] + xyz[2] * transform_matrix[2][2]
    result = [x, y, z]
    return result


def calculate_center_point(doc):
    """
    to calculate element center point

    Parameters
    ----------
    single bdf document

    Returns
    ------
        result : dict
        {elementID:{x: , y: , z: }}

    """

    # calculate all type element center point
    if doc["type"] == "CROD":
        return _calculate_crod_center_point(doc)

    elif doc["type"] == "CBAR":
        return _calculate_cbar_center_point(doc)

    elif doc["type"] == "CSHEAR" or doc["type"] == "CQUAD4":
        return _calculate_cshear_center_point(doc)

    elif doc["type"] == "CTRIA3":
        return _calculate_ctria3_center_point(doc)


def _calculate_crod_center_point(doc):
    """
    to calculate crod center point

    Parameters
    ----------
    doc : dict
        single bdf document

    """
    # calculate center point for crod element
    if doc["nodes_ref"] is None:
        logger.error(
            "reference is missing please double check, type: CTRIA3, element_no:" + str(doc["eid"]))
        return
    else:
        p1_x = doc["nodes_ref"][0]["xyz"][0]
        p1_y = doc["nodes_ref"][0]["xyz"][1]
        p1_z = doc["nodes_ref"][0]["xyz"][2]

        p2_x = doc["nodes_ref"][1]["xyz"][0]
        p2_y = doc["nodes_ref"][1]["xyz"][1]
        p2_z = doc["nodes_ref"][1]["xyz"][2]

    # center point =  ((x1+x2+...+xn)/n, (y1+y2+...+yn)/n, (z1+z2+...+zn)/n)
    center_point = {"x": (p1_x + p2_x) / 2, "y": (p1_y + p2_y) / 2, "z": (p1_z + p2_z) / 2}
    result = {doc["eid"]: {"center": center_point}}
    return center_point


def _calculate_cbar_center_point(doc):
    """
    to calculate cbar center point

    Parameters
    ----------
    doc : dict
        single bdf document

    """
    # calculate center point for cbar
    if doc["ga_ref"] is None:
        logger.error(
            "reference is missing please double check, type: CTRIA3, element_no:" + str(doc["eid"]))
        return
    else:
        p1_x = doc["ga_ref"]["xyz"][0]
        p1_y = doc["ga_ref"]["xyz"][1]
        p1_z = doc["ga_ref"]["xyz"][2]

    if doc["gb_ref"] is None:
        logger.error(
            "reference is missing please double check, type: CTRIA3, element_no:" + str(doc["eid"]))
        return
    else:
        p2_x = doc["gb_ref"]["xyz"][0]
        p2_y = doc["gb_ref"]["xyz"][1]
        p2_z = doc["gb_ref"]["xyz"][2]

    # center point =  ((x1+x2+...+xn)/n, (y1+y2+...+yn)/n, (z1+z2+...+zn)/n)
    center_point = {"x": (p1_x + p2_x) / 2, "y": (p1_y + p2_y) / 2, "z": (p1_z + p2_z) / 2}
    result = {doc["eid"]: {"center": center_point}}
    return center_point


def _calculate_cshear_center_point(doc):
    """
    to calculate cshear center point

    Parameters
    ----------
    doc : dict
        single bdf document

    """
    # calculate center point for cshear
    if doc["nodes_ref"] is None:
        logger.error(
            "reference is missing please double check, type: CTRIA3, element_no:" + str(doc["eid"]))
        return
    else:
        p1_x = doc["nodes_ref"][0]["xyz"][0]
        p1_y = doc["nodes_ref"][0]["xyz"][1]
        p1_z = doc["nodes_ref"][0]["xyz"][2]

        p2_x = doc["nodes_ref"][1]["xyz"][0]
        p2_y = doc["nodes_ref"][1]["xyz"][1]
        p2_z = doc["nodes_ref"][1]["xyz"][2]

        p3_x = doc["nodes_ref"][2]["xyz"][0]
        p3_y = doc["nodes_ref"][2]["xyz"][1]
        p3_z = doc["nodes_ref"][2]["xyz"][2]

        p4_x = doc["nodes_ref"][3]["xyz"][0]
        p4_y = doc["nodes_ref"][3]["xyz"][1]
        p4_z = doc["nodes_ref"][3]["xyz"][2]

    # center point =  ((x1+x2+...+xn)/n, (y1+y2+...+yn)/n, (z1+z2+...+zn)/n)
    center_point = {"x": (p1_x + p2_x + p3_x + p4_x) / 4, "y": (p1_y + p2_y + p3_y + p4_y) / 4,
                    "z": (p1_z + p2_z + p3_z + p4_z) / 4}
    result = {doc["eid"]: {"center": center_point}}
    return center_point


def _calculate_ctria3_center_point(doc):
    """
    to calculate ctria3 center point

    Parameters
    ----------
    doc : dict
        single bdf document

    """
    # calculate center point for ctria3
    if doc["nodes_ref"] is None:
        logger.error(
            "reference is missing please double check, type: CTRIA3, element_no:" + str(doc["eid"]))
        return
    else:
        p1_x = doc["nodes_ref"][0]["xyz"][0]
        p1_y = doc["nodes_ref"][0]["xyz"][1]
        p1_z = doc["nodes_ref"][0]["xyz"][2]

        p2_x = doc["nodes_ref"][1]["xyz"][0]
        p2_y = doc["nodes_ref"][1]["xyz"][1]
        p2_z = doc["nodes_ref"][1]["xyz"][2]

        p3_x = doc["nodes_ref"][2]["xyz"][0]
        p3_y = doc["nodes_ref"][2]["xyz"][1]
        p3_z = doc["nodes_ref"][2]["xyz"][2]

    # center point =  ((x1+x2+...+xn)/n, (y1+y2+...+yn)/n, (z1+z2+...+zn)/n)
    center_point = {"x": (p1_x + p2_x + p3_x) / 3, "y": (p1_y + p2_y + p3_y) / 3, "z": (p1_z + p2_z + p3_z) / 3}
    result = {doc["eid"]: {"center": center_point}}
    return center_point


#########################################################################
#                           gltf模型创建
def create_gltf_from_parsed_bdf(parsed_bdf, gltf_path, xref_path):
    """
    create gltf from bdf data

    Parameters
    ----------
    parsed_bdf : dict
        the parsed bdf - list of
    gltf file_path : str
        the full path to gltf file

    """

    logger.debug("staring to create gltf for bdf model...")
    start_time = time.time()

    # initialize geometry
    geometry = {
        "vertext_map": {
            "cur_index": 0,
            "vertices": None
        },
        "face_geometry": {
            "indices": None
        },
        "edge_geometry": {
            "indices": None,
        },
        "line_geometry":{
            "indices": None
        }
    }

    # 交叉引用
    item_xref = {"item_xref": None, "face_index": 0, "rod_index": 0}

    # 按单元处理bdf
    for element in parsed_bdf:
        element_type = element['type']
        if element_type == 'CQUAD4' or element_type == 'CSHEAR':
            process_cquad4_element(element, geometry, item_xref)
        if element_type == 'CTRIA3':
            process_ctria3_element(element, geometry, item_xref)
        if element_type == 'CBAR':
            process_cbar_element(element, geometry, item_xref)
        if element_type == 'CROD':
            process_crod_element(element, geometry, item_xref)

    # create gltf JSON
    # get vertices for geometry
    vertices = np.array(geometry['vertext_map']['vertices'])
    # 当模型没有杆单元,默认其两个点坐标为第0个点
    if geometry["line_geometry"]["indices"] is None:
        geometry["line_geometry"]["indices"] = [[0,0]]
    # 当模型没有面单元,默认其三个点坐标为第0个点
    if geometry['face_geometry']['indices'] is None:
        geometry['face_geometry']['indices'] = [[0,0,0]]
        geometry['edge_geometry']['indices'] = [[0,0]]

    # get face indices, edge indices and line_indices for geometry
    face_indices = np.array(geometry['face_geometry']['indices'])
    edge_indices = np.array(geometry['edge_geometry']['indices'])
    line_indices = np.array(geometry['line_geometry']['indices'])

    # convert dtype, vertices array to float32, indices array to unsigned two-byte int
    vertices = vertices.astype(np.float32)
    face_indices = face_indices.astype(np.uint32)
    edge_indices = edge_indices.astype(np.uint32)
    line_indices = line_indices.astype(np.uint32)

    # convert indices to one-dimension array
    face_indices = face_indices.flatten()
    edge_indices = edge_indices.flatten()
    line_indices = line_indices.flatten()

    # create gltf artifacts
    # convert vertices to two-dimension binary array
    b_vertices = vertices.tobytes()
    # convert indices to one dimension binary array
    b_face_indices = face_indices.tobytes()
    b_edge_indices = edge_indices.tobytes()
    b_line_indices = line_indices.tobytes()
    b_indices = b_face_indices + b_edge_indices + b_vertices

    # populate gltf JSON data
    gltf = pygltflib.GLTF2(
        scene=0,
        scenes=[pygltflib.Scene(nodes=[0, 1, 2])],
        nodes=[
            pygltflib.Node(name="face_triangles", mesh=0),
            pygltflib.Node(name="edge_line", mesh=1),
            pygltflib.Node(name="rod_line", mesh=2)
        ],
        materials=[
            {
                "pbrMetallicRoughness": {
                    "baseColorFactor": [
                        0.25,
                        0.81,
                        0.81,
                        0
                    ],
                    "metallicFactor": 0.5,
                    "roughnessFactor": 0.5
                },
                "alphaMode": "BLEND",
                "doubleSided": True
            },
            {
                "pbrMetallicRoughness": {
                    "baseColorFactor": [
                        0,
                        1,
                        0,
                        1
                    ],
                    "metallicFactor": 0.5,
                    "roughnessFactor": 0.5
                }
            },
            {
                "pbrMetallicRoughness": {
                    "baseColorFactor": [
                        1,
                        0.5490196078431373,
                        0,
                        1
                    ],
                    "metallicFactor": 0.5,
                    "roughnessFactor": 0.5
                }
            }
        ],
        meshes=[
            pygltflib.Mesh(
                primitives=[
                    pygltflib.Primitive(
                        attributes=pygltflib.Attributes(POSITION=2), indices=0, material=0, mode=4
                    )
                ]
            ),
            pygltflib.Mesh(
                primitives=[
                    pygltflib.Primitive(
                        attributes=pygltflib.Attributes(POSITION=2), indices=1, material=1, mode=1
                    )
                ]
            ),
            pygltflib.Mesh(
                primitives=[
                    pygltflib.Primitive(
                        attributes=pygltflib.Attributes(POSITION=2), indices=3, material=2, mode=1
                    )
                ]
            )
        ],
        cameras=[
            {
                "type": "orthographic",
                "orthographic": {
                    "xmag": 662,
                    "ymag": 500,
                    "zfar": 1000000,
                    "znear": 1
                }
            }
        ],
        accessors=[
            pygltflib.Accessor(
                bufferView=0,
                componentType=pygltflib.UNSIGNED_INT,
                count=face_indices.size,
                type=pygltflib.SCALAR,
                max=[int(face_indices.max())],
                min=[int(face_indices.min())],
            ),
            pygltflib.Accessor(
                bufferView=1,
                componentType=pygltflib.UNSIGNED_INT,
                count=edge_indices.size,
                type=pygltflib.SCALAR,
                max=[int(edge_indices.max())],
                min=[int(edge_indices.min())],
            ),
            pygltflib.Accessor(
                bufferView=2,
                componentType=pygltflib.FLOAT,
                count=len(vertices),
                type=pygltflib.VEC3,
                max=vertices.max(axis=0).tolist(),
                min=vertices.min(axis=0).tolist(),
            ),
            pygltflib.Accessor(
                bufferView=3,
                componentType=pygltflib.UNSIGNED_INT,
                count=line_indices.size,
                type=pygltflib.SCALAR,
                max=[int(line_indices.max())],
                min=[int(line_indices.min())],
            )
        ],
        bufferViews=[
            pygltflib.BufferView(
                buffer=0,
                byteLength=len(b_face_indices),
                target=pygltflib.ELEMENT_ARRAY_BUFFER,
            ),
            pygltflib.BufferView(
                buffer=0,
                byteOffset=len(b_face_indices),
                byteLength=len(b_edge_indices),
                target=pygltflib.ARRAY_BUFFER,
            ),
            pygltflib.BufferView(
                buffer=0,
                byteOffset=len(b_face_indices) + len(b_edge_indices),
                byteLength=len(b_vertices),
                target=pygltflib.ARRAY_BUFFER,
            ),
            pygltflib.BufferView(
                buffer=0,
                byteOffset=len(b_indices),
                byteLength=len(b_line_indices),
                target=pygltflib.ARRAY_BUFFER,
            )
        ],
        buffers=[
            pygltflib.Buffer(
                byteLength=len(b_indices) + len(b_line_indices)
            )
        ],
    )

    # set binary buff
    gltf.set_binary_blob(b_indices + b_line_indices)

    # save gltf file
    gltf.save(gltf_path)

    # save glb file
    file_path, file_extension = os.path.splitext(gltf_path)
    glb_file_path = file_path + '.glb'
    gltf.save(glb_file_path)

    # 压缩json结构并生成json文件
    json_data = {"item_xref": item_xref["item_xref"]}
    with open(xref_path, "w", encoding="utf-8") as f:
        json.dump(json_data, f, indent=4, ensure_ascii=False)

    end_time = time.time()
    logger.debug("create gltf and json successfully in " +
                 str(end_time - start_time) + " seconds")

    print("create gltf and glb successfully in " +
          str(end_time - start_time) + " seconds")


#### bdf helper
def process_cquad4_element(element, geometry, item_xref):
    """
    process single CQUAD4 element and populate geometry

    Parameters
    ----------
    element: dict
        single bdf element
    geometry: dict
        the geometry to hold 3D model data

    """

    # 获取单元id和点信息
    nodes = element['nodes_ref']
    element_no = element['eid']

    if nodes is None:
        logger.error(
            "nodes_ref is missing, type: CQUAD4, element_no:" + str(element_no))
        return

    # 将单元中心点信息及材料信息存入item_xref
    _add_item_xref(element, element_no, item_xref)

    # 生成cquad4单元的点(0,1,2,3)的信息存入geometry['vertext_map']
    for node in nodes:
        # add vertices to vertext_map
        _add_vertex(node, geometry['vertext_map'])
    # 生成cquad4单元的面的信息(0-1-2,2-3-0)存入geometry['face_geometry']
    len = geometry['vertext_map']['cur_index']
    faces = [
        [len - 4, len - 3, len - 2],
        [len - 2, len - 1, len - 4]
    ]
    for face in faces:
        _add_face(face, geometry['face_geometry'], element_no)

        _add_face_json(item_xref)

    # 生成cquad4单元的边缘线的信息(0-1, 1-2, 2-3, 3-0)存入geometry['edge_geometry']
    lines = [
        [len - 4, len - 3], [len - 3, len - 2],
        [len - 2, len - 1], [len - 1, len - 4]
    ]
    for line in lines:
        _add_edge_line(line, geometry['edge_geometry'])


def process_ctria3_element(element, geometry, item_xref):
    """
    process single CTRIA3 element and populate geometry

    Parameters
    ----------
    doc: dict
        single bdf document
    geometry: dict
        the geometry to hold 3D model data
    item_xref: the dict holds element-index and coordinate data

    """
    nodes = element['nodes_ref']
    element_no = element['eid']

    if nodes is None:
        logger.error(
            "nodes_ref is missing, type: CTRIA3, element_no:" + str(element_no))
        return

    # 将单元中心点信息及材料信息存入item_xref
    _add_item_xref(element, element_no, item_xref)

    # 生成ctria3单元的点(0,1,2)的信息存入geometry['vertext_map']
    for node in nodes:
        # add vertices to vertext_map
        _add_vertex(node, geometry['vertext_map'])
    # 生成ctria3单元的面的信息(0-1-2)存入geometry['face_geometry']
    len = geometry['vertext_map']['cur_index']
    _add_face([len - 3, len - 2, len - 1],
              geometry['face_geometry'], element_no)

    _add_face_json(item_xref)

    # 生成ctria3单元的边缘线的信息(0-1, 1-2, 2-0)存入geometry['edge_geometry']
    lines = [
        [len - 3, len - 2], [len - 2, len  - 1], [len - 1, len - 3]
    ]
    for line in lines:
        _add_edge_line(line, geometry['edge_geometry'])


def process_cbar_element(element, geometry, item_xref):
    """
    process single CBAR element and populate geometry

    Parameters
    ----------
    doc: dict
        single bdf document
    geometry: dict
        the geometry to hold 3D model data
    item_xref: the dict holds element-index and coordinate data

    """
    # populate 3D cube for CBAR element
    ga = element['ga_ref']
    gb = element['gb_ref']
    element_no = element['eid']

    if (ga is None) or (gb is None):
        logger.error(
            "reference is missing please double check, type: CBAR, element_no:" + str(element_no))
        return

    # 将单元中心点信息及材料信息存入item_xref
    _add_item_xref(element, element_no, item_xref)

    # 生成cbar杆单元的点(0,1)的信息存入geometry['vertext_map']
    _add_vertex(ga, geometry['vertext_map'])
    _add_vertex(gb, geometry['vertext_map'])
    # 生成cbar杆单元的线的信息(0-1)存入geometry['line_geometry']
    len = geometry['vertext_map']['cur_index']
    _add_rod_line([len - 2, len - 1], geometry['line_geometry'], element_no)

    # 生成杆和index信息存入item_xref
    _add_rod_json(item_xref)


def process_crod_element(element, geometry, item_xref):
    """
    process single CROD element and populate geometry

    Parameters
    ----------
    doc: dict
        single bdf document
    geometry: dict
        the geometry to hold 3D model data
    item_xref: the dict holds element-index and coordinate data

    """
    # populate 3D cube for CROD element
    nodes = element['nodes_ref']
    element_no = element['eid']

    if nodes is None:
        logger.error(
            "reference is missing please double check, type: CROD, element_no:" + str(element_no))
        return

    # 将单元中心点信息及材料信息存入item_xref
    _add_item_xref(element, element_no, item_xref)

    # 生成crod杆单元的点(0,1)的信息存入geometry['vertext_map']
    for node in nodes:
        _add_vertex(node, geometry["vertext_map"])
    # 生成crod杆单元的线的信息(0-1)存入geometry['line_geometry']
    len = geometry['vertext_map']['cur_index']
    _add_rod_line([len - 2, len - 1], geometry['line_geometry'], element_no)

    _add_rod_json(item_xref)


def _add_vertex(node, vertext_map):
    """
    add node to vertex

    Parameters
    ----------
    node: a single node
    vertext_map: vertex object holds all vertex infomation

    """
    # populate vertices array
    if vertext_map['vertices'] is None:
        vertext_map['vertices'] = []
    vertext_map['vertices'].append(
        [node['xyz'][0],
         node['xyz'][1],
         node['xyz'][2]])
    vertext_map['cur_index'] = vertext_map['cur_index'] + 1


def _add_face(face, geometry, element_no):
    """
    add face to geometry

    Parameters
    ----------
    face: a single face indices such as [0,1,2]
    geometry: the geometry to hold 3D model data
    element_no: the related bdf element no

    """
    # add to face geometry
    if geometry['indices'] is None:
        geometry['indices'] = []
    geometry['indices'].append(face)


def _add_edge_line(line, geometry):
    """
    add edge line to geometry

    Parameters
    ----------
    line: a single line indices such as [0,1]
    geometry: the geometry holds 3D model data

    """
    if geometry['indices'] is None:
        geometry['indices'] = []
    geometry['indices'].append(line)


def _add_rod_line(line, geometry, element_no):
    """
    add rod line to geometry

    Parameters
    ----------
    line: a single line indices such as [0,1]
    geometry: the geometry holds 3D model data
    element_no: the related bdf element no

    """
    if geometry['indices'] is None:
        geometry['indices'] = []
    geometry['indices'].append(line)


def _add_item_xref(doc, element_no, item_xref):
    """
    add center axis info and material properties to json

    Parameters
    ----------
    doc: single bdf document
    element_no: the related bdf element no
    item_xref: the dict holds element-index and coordinate data
    """

    if item_xref["item_xref"] is None:
        item_xref["item_xref"] = []

    # 计算单元的中心点坐标
    point = calculate_center_point(doc)

    # 获取该单元的材料属性
    properties = _get_element_properties(doc)
    nodes = _get_nodes(doc)
    unit_normal = _get_element_direction(doc)

    face_json = {"item_type": doc['type'],
                 "item_no": str(element_no),
                 "axis_of_center":[point["x"],point["y"],point["z"]],
                 "nodes": nodes,
                 "unit_normal": unit_normal,
                 "properties": properties}
    item_xref["item_xref"].append(face_json)

def _get_nodes(doc):
    """
    get material properties of an element

    Parameters
    ----------
    doc: single bdf document
    element_no: the related bdf element no
    item_xref: the dict holds element-index and coordinate data

    """
    # 获取单元节点
    if (doc['type'] in ['CSHEAR', 'CQUAD4', 'CTRIA3', 'CROD']):
        nodes_ref = doc['nodes_ref']
    elif (doc['type'] in ['CBAR']):
        node_a_ref = doc['ga_ref']
        node_b_ref = doc['gb_ref']
        nodes_ref = [node_a_ref, node_b_ref]
    else:
        print('unknown element type')

    nodes = [{} for _ in range(len(nodes_ref))]
    for i, node_ref in enumerate(nodes_ref):
        nodes[i]['nid'] = node_ref['nid']
        nodes[i]['xyz'] = node_ref['xyz']

    return nodes

def _get_element_properties(doc):
    """
    get material properties of an element

    Parameters
    ----------
    doc: single bdf document
    element_no: the related bdf element no
    item_xref: the dict holds element-index and coordinate data

    """
    # 获取单元属性
    properties = doc['pid_ref']
    if '_comment' in properties:
        properties.pop('_comment')

    return properties

def _get_element_direction(doc):

    if (doc['type'] in ['CSHEAR', 'CQUAD4', 'CTRIA3']):
        nodes_ref = doc['nodes_ref']
        points = []
        for i, node_ref in enumerate(nodes_ref):
            points.append(np.array(node_ref['xyz']))
        v1 = points[1] - points[0]
        v2 = points[2] - points[0]
        normal = np.cross(v1, v2)
        norm = np.linalg.norm(normal)
        unit_normal = normal/norm

    elif (doc['type'] in ['CROD']):
        nodes_ref = doc['nodes_ref']
        points = []
        for i, node_ref in enumerate(nodes_ref):
            points.append(np.array(node_ref['xyz']))
        normal = points[1] - points[0]
        norm = np.linalg.norm(normal)
        unit_normal = normal/norm

    elif (doc['type'] in ['CBAR']):
        node_a = doc['ga_ref']
        node_b = doc['gb_ref']
        points = [np.array(node_a['xyz']), np.array(node_b['xyz'])]
        normal = points[1] - points[0]
        norm = np.linalg.norm(normal)
        unit_normal = normal/norm

    else:
        print('unknow element type')
    unit_normal = unit_normal.tolist()

    return unit_normal

def _add_face_json(item_xref):
    """
    add face_id-index to json

    Parameters
    ---------
    item_xref: the dict holds element-index and coordinate data

    """
    if "face_id" not in item_xref["item_xref"][-1].keys():
        item_xref["item_xref"][-1].update({"face_id":[]})
    item_xref["item_xref"][-1]["face_id"].append(item_xref["face_index"])
    item_xref["face_index"] = item_xref["face_index"] + 1

def _add_rod_json(item_xref):
    """
    add rod_id-index to json

    Parameters
    ----------
    item_xref: the dict holds element-index and coordinate data

    """
    if "rod_id" not in item_xref["item_xref"][-1].keys():
        item_xref["item_xref"][-1].update({"rod_id":[]})
    item_xref["item_xref"][-1]["rod_id"].append(item_xref["rod_index"])
    item_xref["rod_index"] = item_xref["rod_index"] + 1


